﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace NaturalNote.Core
{
    public class MemberProperty
    {
        private delegate void PropertySetter(object target, object value);
        private delegate object PropertyGetter(object target);
        private const string Fast = "Fast";
        private readonly PropertyInfo _info;
        private readonly object _lockObj = new object();
        private readonly MemberProperty.PropertySetter _setter;
        private readonly MemberProperty.PropertyGetter _getter;
        internal PropertyInfo PropertyInfo
        {
            get
            {
                return this._info;
            }
        }
        public Type ValueType
        {
            get
            {
                return this._info.PropertyType;
            }
        }
        public MemberProperty(PropertyInfo pi)
        {
            if (pi != null)
            {
                this._info = pi;
                if (this._info.CanWrite)
                {
                    this._setter = MemberProperty.GetPropertySetter(this._info);
                }
                if (this._info.CanRead)
                {
                    this._getter = MemberProperty.GetPropertyGetter(this._info);
                }
            }
        }
        public void SetProperty(object destination, object value)
        {
            if (this._setter != null && destination != null)
            {
                this._setter(destination, value);
            }
        }
        public object GetProperty(object source)
        {
            if (this._getter != null && source != null)
            {
                object value = this._getter(source);
                return value;
            }
            return null;
        }
        private static MemberProperty.PropertyGetter GetPropertyGetter(PropertyInfo propertyDetails)
        {
            MethodInfo getMethod = propertyDetails.GetGetMethod(true);
            DynamicMethod newMethod;
            if (!propertyDetails.DeclaringType.IsInterface)
            {
                newMethod = new DynamicMethod("Fast" + getMethod.Name, typeof(object), new Type[]
                {
                    typeof (object)
                }, propertyDetails.DeclaringType, true);
            }
            else
            {
                newMethod = new DynamicMethod("Fast" + getMethod.Name, typeof(object), new Type[]
                {
                    typeof (object)
                }, propertyDetails.DeclaringType.Assembly.ManifestModule, true);
            }
            ILGenerator il = newMethod.GetILGenerator();
            il.DeclareLocal(typeof(object));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, propertyDetails.DeclaringType);
            il.EmitCall(OpCodes.Callvirt, getMethod, null);
            if (getMethod.ReturnType.IsValueType)
            {
                il.Emit(OpCodes.Box, getMethod.ReturnType);
            }
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);
            return newMethod.CreateDelegate(typeof(MemberProperty.PropertyGetter)) as MemberProperty.PropertyGetter;
        }
        private static MemberProperty.PropertySetter GetPropertySetter(PropertyInfo propertyDetails)
        {
            MethodInfo setMethod = propertyDetails.GetSetMethod(true);
            DynamicMethod newMethod;
            if (!propertyDetails.DeclaringType.IsInterface)
            {
                newMethod = new DynamicMethod("Fast" + setMethod.Name, null, new Type[]
                {
                    typeof (object),
                    typeof (object)
                }, propertyDetails.DeclaringType, true);
            }
            else
            {
                newMethod = new DynamicMethod("Fast" + setMethod.Name, null, new Type[]
                {
                    typeof (object),
                    typeof (object)
                }, propertyDetails.DeclaringType.Assembly.ManifestModule, true);
            }
            ILGenerator il = newMethod.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, propertyDetails.DeclaringType);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Unbox_Any, propertyDetails.PropertyType);
            il.EmitCall(OpCodes.Callvirt, setMethod, null);
            il.Emit(OpCodes.Ret);
            return newMethod.CreateDelegate(typeof(MemberProperty.PropertySetter)) as MemberProperty.PropertySetter;
        }
    }
}
